
package com.borqs.music;

import static com.borqs.music.tasks.QueryUploadTask.MODE_LIST;
import static com.borqs.music.tasks.QueryUploadTask.MODE_SINGLE;
import static com.borqs.music.util.AccountUtils.BORQS_ACCOUNT_OPTIONS_KEY_SESSION;

import java.io.File;
import java.util.LinkedList;
import java.util.Queue;

import org.json.JSONArray;
import org.json.JSONException;

import android.app.Service;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.preference.PreferenceManager;
import android.provider.MediaStore.Audio;
import android.text.TextUtils;

import com.borqs.music.database.DBController;
import com.borqs.music.executor.MusicExecutor;
import com.borqs.music.executor.Task;
import com.borqs.music.executor.TaskAdapter;
import com.borqs.music.executor.TaskListener;
import com.borqs.music.model.SongInfo;
import com.borqs.music.tasks.QueryUploadLyricTask;
import com.borqs.music.tasks.QueryUploadTask;
import com.borqs.music.tasks.UploadFavoriteTask;
import com.borqs.music.tasks.UploadLyricTask;
import com.borqs.music.tasks.UploadStatisticsTask;
import com.borqs.music.tasks.UploadTask;
import com.borqs.music.ui.SignupUploadActivity;
import com.borqs.music.util.AccountUtils;
import com.borqs.music.util.AsyncLyricLoader;
import com.borqs.music.util.MusicConf;
import com.borqs.music.util.MusicUtils;
import com.borqs.music.util.MyLogger;

public class TransferService extends Service {
    static final MyLogger logger = MyLogger.getLogger("TransferService");

    public static final String ACTION_START_UPLOAD_SONG = "com.borqs.music.ACTION_START_UPLOAD";
    public static final String ACTION_START_UPLOAD_LYRIC = "com.borqs.music.ACTION_START_UPLOAD_LYRIC";
    public static final String ACTION_START_UPLOAD_STATISTICS = "com.borqs.music.ACTION_START_UPLOAD_STATISTICS";
    public static final String ACTION_START_UPLOAD_FAVORITE = "com.borqs.music.ACTION_START_UPLOAD_FAVORITE";
    public static final String ACTION_SYNC_SONG_ALARM = "com.borqs.music.ACTION_SYNC_SONG_ALARM";
    public static final String ACTION_SYNC_FAVORITE_ALRAM = "com.borqs.music.ACTION_SYNC_FAVORITE_ALARM";

    private String mTicket = "";
    // should only one uploading process.
    private boolean isUploading = false;
    private String mCurrentTask = "";
    private Queue<String> mTaskQueue = new LinkedList<String>();
    private DBController mDBController;

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private Handler uploadHandler;

    @Override
    public void onCreate() {
        super.onCreate();

        HandlerThread uploadThread = new HandlerThread("Sync Thread");
        uploadThread.start();
        uploadHandler = new UploadHandler(uploadThread.getLooper());
        mDBController = DBController.getInstance(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        logger.d("TransferService starting");
        if (intent == null) {
            return START_STICKY_COMPATIBILITY;
        }

        boolean isPreload = AccountUtils.isBorqsAccountServicePreloaded(this);
        if (!isPreload) {
            logger.d("Can't access the BorqsAccountService");
            stopSelf();
        }
        String ticketStr = AccountUtils.getUserData(this, BORQS_ACCOUNT_OPTIONS_KEY_SESSION);
        // check ticket, UploTASK_UPLOAD_LYRICad must have a ticket
        if (ticketStr != null && ticketStr.length() > 0) {
            mTicket = ticketStr;
        } else {
            logger.d("No ticket found!");
            stopSelf();
        }

        if (!MusicUtils.isSdcardUsable()) { // can't access the SDCARD
            logger.d("can't access the SDCARD");
            stopSelf();
        }

        if (!MusicUtils.isNetworkEnabled(this)) {
            logger.d("Network disable, just exit.");
            stopSelf();
        }

        String action = intent.getAction();
        if (isUploading) {
            logger.d("Another service is running, cache with next task.");
            if (!mCurrentTask.equals(action) && !mTaskQueue.contains(action)) {
                mTaskQueue.offer(action);
            }
        } else {
            isUploading = true;
            mCurrentTask = action;

            if (ACTION_START_UPLOAD_SONG.equals(action)) {
                if (!isUploadEnable()) {
                    logger.d("isAutoUploadEnable = false");
                    quitService();
                }else{
                    startUploadSong();
                }
            } else if (ACTION_START_UPLOAD_LYRIC.equals(action)) {
                startUploadLyric();
            }else if (ACTION_START_UPLOAD_STATISTICS.equals(action)) {
                startUploadStatistics();
            } else if (ACTION_START_UPLOAD_FAVORITE.equals(action)) {
                startUploadFavorite();
            } else {
                logger.d("Action doesn't match! Stop service.");
                stopSelf();
            }
        }

        // We want this service to continue running until it is explicitly
        // stopped, so return sticky.
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        logger.d("TransferService stop");
    }

    private static final int START_UPLOAD_SONG = 1;
    private static final int START_UPLOAD_LYRIC = 2;
    private static final int QUERY_UPLOAD_SONG_LIST = 3;
    private static final int QUERY_UPLOAD_SINGLE = 4;
    private static final int QUERY_UPLOAD_LYRIC_SINGLE = 5;

    private class UploadHandler extends Handler {
        public UploadHandler(Looper looper) {
            super(looper);
        }

        TaskListener querySongListener = new TaskAdapter() {
            @Override
            public void onDone(Task task) {
                final QueryUploadTask qTask = (QueryUploadTask) task;

                if (qTask.getMode() == MODE_LIST) {
                    // TODO Handle the query list
                    throw new IllegalAccessError("Shouldn't be here: MODE_LIST");
                } else if (qTask.getMode() == MODE_SINGLE) {
                    SongInfo song = (SongInfo) qTask.getResult();
                    logger.d("NAME = " + song.fileName + ", NEED SYNC = " + qTask.isNeedToUpload());
                    // song = mDBController.querySongByMD5(song.fileMd5);
                    // boolean dataFilled = fillSongInfoAccordingScannerDB(song,
                    // song.id,
                    // song.dateAdded, song.fileSize);

                    // if (dataFilled) {
                    File f = new File(song.fileUrl);
                    if (f.exists() && qTask.isNeedToUpload()) {
                        Message msg = uploadHandler.obtainMessage();
                        msg.what = START_UPLOAD_SONG;
                        msg.obj = song;
                        msg.sendToTarget();
                    } else { // file not exist or no need upload
                        mDBController.markSongAsUploaded(song.fileMd5);
                        startUploadSong();
                    }
                    // } else { // fill data failed
                    // startUploadSong();
                    // }
                }
            }

            @Override
            public void onFail(Task task) {
                logger.d("query synchronized Task failed.");
                quitService();
            }
        };

        TaskListener queryLyricListener = new TaskAdapter() {
            @Override
            public void onDone(Task task) {
                QueryUploadLyricTask qtask = (QueryUploadLyricTask) task;
                SongInfo song = (SongInfo) qtask.getResult();
                if (qtask.isNeedtoUpload()) {
                    Message msg = uploadHandler.obtainMessage();
                    msg.what = START_UPLOAD_LYRIC;
                    msg.obj = song;
                    msg.sendToTarget();
                } else {// file not exist or no need upload
                    mDBController.markLyricAsUploaded(song.fileMd5);
                    startUploadLyric();
                }
            }

            @Override
            public void onFail(Task task) {
                logger.d("query synchronized Task failed.");
                quitService();
            }
        };

        TaskListener uploadSongListener = new TaskAdapter() {
            @Override
            public void onDone(Task task) {
                SongInfo song = (SongInfo) task.getResult();
                mDBController.markSongAsUploaded(song.fileMd5);
                logger.d("Upload song '" + song.title + "' success");
                startUploadSong();
            }

            @Override
            public void onFail(Task task) {
                logger.d("uploadListener Task failed.");
                quitService();
            }
        };

        TaskListener uploadLyricListener = new TaskAdapter() {
            @Override
            public void onDone(Task task) {
                SongInfo song = (SongInfo) task.getResult();
                mDBController.markLyricAsUploaded(song.fileMd5);
                logger.d("Upload song " + song.title + " lyric success.");
                startUploadLyric();
            }

            @Override
            public void onFail(Task task) {
                logger.d("uploadLyricListener Task failed.");
                quitService();
            }
        };

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {

                case QUERY_UPLOAD_SONG_LIST: {
                    // TODO Handle the query list
                    throw new IllegalAccessError("Shouldn't be here");
                }

                case QUERY_UPLOAD_SINGLE: {
                    SongInfo song = (SongInfo) msg.obj;
                    try {
                        QueryUploadTask task = new QueryUploadTask(querySongListener, mTicket, song);
                        MusicExecutor.executeTask(task);
                    } catch (Exception ex) {
                        logger.d("Create QueryUploadTask Task failed.");
                        ex.printStackTrace();
                        querySongListener.onFail(null);
                    }
                    return;
                }

                case QUERY_UPLOAD_LYRIC_SINGLE: {
                    logger.d("query upload lyric single");
                    SongInfo song = (SongInfo) msg.obj;
                    try {
                        QueryUploadLyricTask task = new QueryUploadLyricTask(queryLyricListener,
                                mTicket, song);
                        MusicExecutor.executeTask(task);
                    } catch (Exception ex) {
                        logger.d("Create QueryUploadLyricTask Task failed.");
                        ex.printStackTrace();
                        queryLyricListener.onFail(null);
                    }
                    return;
                }

                case START_UPLOAD_SONG: {
                    if (MusicUtils.isSdcardUsable() && isUploadEnable()) {
                        SongInfo song = (SongInfo) msg.obj;
                        logger.d("start sync song title = " + song.title);
                        if (song != null && mTicket != null && mTicket.length() > 0) {
                            UploadTask uploadTask = new UploadTask(uploadSongListener, song, mTicket);
                            MusicExecutor.executeTask(uploadTask);
                        }
                    } else {
                        quitService();
                    }
                    return;
                }

                case START_UPLOAD_LYRIC: {
                    if (MusicUtils.isSdcardUsable() && isUploadEnable()) {
                        SongInfo song = (SongInfo) msg.obj;
                        logger.d("start sync lyric title = " + song.title);
                        if (song != null && mTicket != null && mTicket.length() > 0) {
                            UploadLyricTask uploadLyricTask = new UploadLyricTask(
                                    uploadLyricListener, song, mTicket);
                            MusicExecutor.executeTask(uploadLyricTask);
                        }
                    } else {
                        quitService();
                    }
                    return;
                }
            }
        }

    }

    private void startUploadSong() {
        if (!isUploadEnable()) {
            logger.d("isAutoUploadEnable = false");
            quitService();
            return;
        }

        SongInfo song = mDBController.queryNextUploadSong();

        if (song != null) {
            boolean dataFilled = fillSongInfoAccordingScannerDB(song, song.id, song.dateAdded,
                    song.fileSize);
            if (dataFilled) {
                logger.d("check NAME = " + song.fileName);
                Message msg = uploadHandler.obtainMessage();
                msg.what = QUERY_UPLOAD_SINGLE;
                msg.obj = song;
                msg.sendToTarget();
            } else {
                mDBController.markSongAsUploaded(song.fileMd5);
                startUploadSong();
            }
        } else {
            allSongsUploaded();
        }
    }

    private void startUploadLyric() {
        logger.d("startUploadLyric() > enter");
        if (!isUploadEnable()) {
            logger.d("isAutoUploadEnable = false");
            quitService();
            return;
        }

        SongInfo song = mDBController.queryNextUploadLyric();

        if (song != null) {

            boolean dataFilled = fillSongInfoAccordingScannerDB(song, song.id, song.dateAdded,
                    song.fileSize);

            if (dataFilled && !TextUtils.isEmpty(song.lyrics)) {
                song.isLocalLyric = AsyncLyricLoader.isLocalLyric(song.fileUrl);
                logger.d("check lyric, song name = " + song.fileName);
                Message msg = uploadHandler.obtainMessage();
                msg.what = QUERY_UPLOAD_LYRIC_SINGLE;
                msg.obj = song;
                msg.sendToTarget();
            } else {
                mDBController.markLyricAsUploaded(song.fileMd5);
                startUploadLyric();
            }
        } else {

            // Write a flag for next upload
            SharedPreferences prefs = getSharedPreferences("Music", Context.MODE_WORLD_READABLE
                    | Context.MODE_WORLD_WRITEABLE);
            Editor editor = prefs.edit();
            editor.putLong(MusicConf.UploadKeys.KEY_LAST_UPLOAD_LYRICS_TIME, System
                    .currentTimeMillis());
            editor.putBoolean(MusicConf.UploadKeys.KEY_NEED_UPLOAD_LYRICS, false);
            editor.commit();

            quitService();
        }
    }

    private void startUploadStatistics() {
        String statisticsJSON = null;
        try {
            statisticsJSON = mDBController.getStatisticsJSON();
        } catch (JSONException e) {
            logger.d("JSONException occured, just clear the statistics table and terminate uploading.");
            mDBController.deleteStatisticsTable();
            quitService();
            return;
        }
        if (null == statisticsJSON) {
            logger.d("Statistics data is null, just terminate uploading.");
            quitService();
            return;
        }
        SharedPreferences prefs = getSharedPreferences("Music",
                Context.MODE_WORLD_READABLE | Context.MODE_WORLD_WRITEABLE);
        Editor editor = prefs.edit();
        editor.putBoolean(MusicConf.UploadKeys.KEY_NEED_UPLOAD_STATISTICS, true);
        editor.commit();
        UploadStatisticsTask task = new UploadStatisticsTask(mUploadStatListener, statisticsJSON,
                mTicket);
        MusicExecutor.executeTask(task);
        quitService();
    }

    TaskListener mUploadStatListener = new TaskAdapter() {
        public void onDone(com.borqs.music.executor.Task task) {
            Boolean result = (Boolean) task.getResult();
            if (result) {
                mDBController.deleteStatisticsTable();
                SharedPreferences prefs = getSharedPreferences("Music",
                        Context.MODE_WORLD_READABLE | Context.MODE_WORLD_WRITEABLE);
                Editor editor = prefs.edit();
                editor.putBoolean(MusicConf.UploadKeys.KEY_NEED_UPLOAD_STATISTICS, false);
                editor.putLong(MusicConf.UploadKeys.KEY_LAST_UPLOAD_STATISTICS_TIME,
                        System.currentTimeMillis());
                editor.commit();
                logger.d("upload statistics successfully.");
            }
        }
    };

    void allSongsUploaded() {
        logger.d("Perfected, all songs synchronized! :)");
        logger.d("Start sync favorite.");
        startUploadFavorite();
    }

    /**
     * Stop the service.
     */
    void quitService() {
        logger.d("quitService(), current task :" + mCurrentTask +"; Queue task size " + mTaskQueue.size());
        if (mTaskQueue.size() > 0) {
            String taskAction = mTaskQueue.poll();
            mCurrentTask = taskAction;
            if (ACTION_START_UPLOAD_SONG.equals(taskAction)) {
                startUploadSong();
            } else if (ACTION_START_UPLOAD_LYRIC.equals(taskAction)) {
                startUploadLyric();
            }else if (ACTION_START_UPLOAD_STATISTICS.equals(taskAction)) {
                startUploadStatistics();
            } else if (ACTION_START_UPLOAD_FAVORITE.equals(taskAction)) {
                startUploadFavorite();
            } else {
                quitService();
            }
        } else {
            stopSelf();
        }

    }

    boolean isUploadEnable() {
        ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo info = cm.getActiveNetworkInfo();
        boolean isWifiNetWork = (info != null && "WIFI".equalsIgnoreCase(info.getTypeName()));
        // I only worked for WIFI
        return isWifiNetWork && SignupUploadActivity.isAbleToUpload(this);
    }

    boolean isAutoUploadFavorites() {
        if (isUploadEnable()) {
            SharedPreferences prefConf = PreferenceManager.getDefaultSharedPreferences(this);
            return prefConf.getBoolean(MusicConf.SettingKeys.KEY_AUTO_UPLOAD_FAVORITES, true);
        } else {
            return false;
        }
    }

    void startUploadFavorite() {
        if (!isAutoUploadFavorites()) {
            logger.d("Sync favorite songs is disabled.");
            quitService();
            return;
        }

        final String KEY_JSON_FOR_SYNC = "json_favor_for_sync";
        final SharedPreferences prefs = getSharedPreferences("Music",
                Context.MODE_WORLD_READABLE | Context.MODE_WORLD_WRITEABLE);
        String jsonForSync = prefs.getString(KEY_JSON_FOR_SYNC, "");
        if (!TextUtils.isEmpty(jsonForSync)) {
            UploadFavoriteTask uploadFavoriteTask = null;
            try {
                JSONArray jArry = new JSONArray(jsonForSync);
                uploadFavoriteTask = new UploadFavoriteTask(new TaskAdapter() {
                    @Override
                    public void onFail(Task task) {
                        logger.d("Synchronized favorite list task failed!");
                        logger.d("Failed sync json : " + task.getResult());
                    }

                    @Override
                    public void onDone(Task task) {
                        Editor edit = prefs.edit();
                        edit.remove(KEY_JSON_FOR_SYNC);
                        edit.commit();
                        quitService();
                        logger.d("Synchronized favorite list successfully !");
                    }
                }, jArry, mTicket);
                MusicExecutor.executeTask(uploadFavoriteTask);
            } catch (JSONException e) {
                logger.d("Synchronized favorite list failed !");
                e.printStackTrace();
            }
        } else {
            logger.d("Do not have any favorite song to synchronize.");
        }

        quitService();
    }

    boolean fillSongInfoAccordingScannerDB(SongInfo song, long songId, long dateAdded, long fileSize) {
        boolean retValue = false;
        Uri uri = Audio.Media.EXTERNAL_CONTENT_URI;
        String selection = Audio.Media._ID + " = ? ";
        String[] selectionArgs = {
                "" + songId
        };
        String[] projectionIn = {
                Audio.Media.DATE_ADDED,
                Audio.Media.SIZE,
                Audio.Media.DATA,
                Audio.Media.TITLE,
                Audio.Media.DISPLAY_NAME,
                Audio.Media.ARTIST,
                Audio.Media.ALBUM,
                Audio.Media.YEAR,
                Audio.Media.DURATION
            };
        ContentResolver res = getContentResolver();
        Cursor cursor = res.query(uri, projectionIn, selection, selectionArgs, null);

        if (cursor != null && cursor.moveToFirst()) {
            long audioSize = cursor.getLong(cursor.getColumnIndex(Audio.Media.SIZE));
            long audioDateAdded = cursor.getLong(cursor.getColumnIndex(Audio.Media.DATE_ADDED));

            if ((audioSize == fileSize) && (dateAdded == audioDateAdded)) {
                retValue = true;
                song.title = cursor.getString(cursor.getColumnIndex(Audio.Media.TITLE));
                song.artist = cursor.getString(cursor.getColumnIndex(Audio.Media.ARTIST));
                song.fileName = cursor.getString(cursor.getColumnIndex(Audio.Media.DISPLAY_NAME));
                song.fileUrl = cursor.getString(cursor.getColumnIndex(Audio.Media.DATA));
                song.album = cursor.getString(cursor.getColumnIndex(Audio.Media.ALBUM));
                song.year = cursor.getString(cursor.getColumnIndex(Audio.Media.YEAR));
                song.duration = cursor
                        .getInt(cursor.getColumnIndex(Audio.Media.DURATION));
                song.fileSize = fileSize;
            }
        }
        if (cursor != null) {
            cursor.close();
        }
        return retValue;
    }
}
